interface Class2Type {
  [key: string]: string;
}

export function _typeof(value: any) {
  const class2type: Class2Type = {};
  "Boolean Number String Function Array Date RegExp Object Error"
    .split(" ")
    .forEach((key) => {
      class2type["[object " + key + "]"] = key.toLowerCase();
    });

  if (value === null) return String(value);

  return typeof value === "object" || typeof value === "function"
    ? class2type[class2type.toString.call(value)] || "object"
    : typeof value;
}

/**
 * websocket 类
 * @class
 * @param {object|string} options-传递参数
 * @param {string} options.url-websocket 连接地址
 * @param {string|object[]} [options.protocol]-子协议
 * @param {reconnect} [options.reconnect]-断开后是否自动连接
 * @param {number} [reconnectIntervalInMilliSeconds]-连接时间
 **/

interface Options {
  url: string;
  reconnect?: boolean;
  reconnectIntervalInMilliSeconds?: number;
  headers?: string[];
}

interface Watcher {
  open: any[];
  message: any[];
  close: any[];
  error: any[];
}

type WebSocketPayload = string | ArrayBuffer | Blob;

interface WebSocketLike {
  close(): any;
  send(data: WebSocketPayload): any;
  onopen: ((event: any) => any) | null;
  onclose: ((event: any) => any) | null;
  onmessage: ((event: any) => any) | null;
  onerror: ((event: any) => any) | null;
  readyState: number;
}

const defaultOptions = {
  url: "",
  reconnect: true,
  reconnectIntervalInMilliSeconds: 0,
  headers: [],
};

class WebSocketClass {
  private ws: WebSocketLike | null;
  private reconnectTimeOut: null | number;
  private readonly url: string;
  private readonly headers: string[];
  private readonly reconnect: boolean;
  private readonly reconnectIntervalInMilliSeconds: number;
  private isDestroy: boolean;
  private attempts: number;
  private serverHeartTimeout: null | number;
  private heartTimeout: null | number;
  private heartTime: number;
  private heart: boolean;
  private watcher: Watcher;
  private isReconnected: boolean;

  constructor(options: string | Options) {
    let _options = {};
    if (_typeof(options) === "string") {
      _options = { url: options };
    } else if (_typeof(options) === "object" && options !== null) {
      _options = options;
    }

    const option = Object.assign({}, defaultOptions, _options);

    this.ws = new WebSocket(option.url, option.headers);
    // this.ws = new WebSocket(option.url);
    this.url = option.url;
    this.headers = option.headers;
    this.reconnect = option.reconnect;
    this.reconnectIntervalInMilliSeconds =
      option.reconnectIntervalInMilliSeconds;
    this.attempts = 1;
    this.isDestroy = false;

    this.reconnectTimeOut = null;

    this.heart = false;
    this.heartTime = 40000;
    this.heartTimeout = null;
    this.serverHeartTimeout = null;
    this.isReconnected = false; // 是否在重连中

    this.watcher = {
      open: [],
      message: [],
      error: [],
      close: [],
    };

    this._init();
  }

  // Websocket 开启连接时执行
  onOpen(callback: () => void) {
    this.watcher["open"].push(callback);
  }

  // websocket 在通信中执行函数
  onMessage(callback: (e: any) => void) {
    this.watcher["message"].push(callback);
  }

  // websocket 断开连接执行函数
  onClose(callback: () => void) {
    this.watcher["close"].push(callback);
  }

  // 销毁当前websocket
  destroy() {
    this.isDestroy = true;
    if (this.ws) this.ws.close();
    this.ws = null;

    // 清除延时
    clearTimeout(this.heartTimeout!);
    clearTimeout(this.serverHeartTimeout!);
    clearTimeout(this.reconnectTimeOut!);
    this.heartTimeout = null;
    this.serverHeartTimeout = null;
    this.reconnectTimeOut = null;
  }

  // 重连
  doReconnect() {
    if (this.reconnect) {
      // 存在ws 则先关闭
      if (this.ws) {
        this.ws.close();
        this.ws = null;
      }

      // 主动destroy，不进行重连
      if (this.isDestroy) return;

      // 重连判断 在重连中则返回
      if (this.isReconnected) return;
      this.isReconnected = true;

      const time = this._generateInterval(this.attempts);
      this.reconnectTimeOut = window.setTimeout(() => {
        this.attempts += 1;
        this.isReconnected = false;
        this.ws = new WebSocket(this.url, this.headers);
        this._init();
      }, time);
    }
  }

  // 初始化
  _init() {
    this.ws!.onopen = () => {
      this.watcher["open"].forEach((fn) => fn());
      this.sendHeart();
    };

    this.ws!.onmessage = (event: any) => {
      // 过滤心跳数据
      let data = {};
      try {
        data = JSON.parse(event.data);
      } catch (error) {
        data = event.data;
      }
      // if (data.c !== "r" || data.r !== "h") {
      this.watcher["message"].forEach((fn) => fn(data));
      // }
      this.sendHeart();
    };

    this.ws!.onclose = () => {
      this.watcher["close"].forEach((fn) => fn());
      this.doReconnect();
    };

    this.ws!.onerror = () => {
      // 默认ws报错就执行重连 todo -- 解决第一次连接失败
      this.doReconnect();
    };
  }

  // 监听心跳 重连
  sendHeart() {
    const that = this;
    clearTimeout(this.heartTimeout!);
    clearTimeout(this.serverHeartTimeout!);

    // if (!this.isDestroy) {
    //   // ws 已经删除不做心跳重连
    //   this.heartTimeout = window.setTimeout(() => {
    //     // 发送心跳
    //     // if (this.ws && this.ws.readyState === 1) {
    //     //   this.ws.send(JSON.stringify({ c: "h" }));
    //     // }

    //     that.serverHeartTimeout = window.setTimeout(() => {
    //       // 如果超过一定时间还没重置，说明后端断开了
    //       that.doReconnect();
    //     }, that.heartTime);
    //   }, that.heartTime);
    // }
  }

  // websocket 发送消息
  sendMessage(message: object | string) {
    const that = this;
    if (message && this.ws) {
      try {
        this.ws.send(JSON.stringify(message));
      } catch (err) {
        const timer = setInterval(() => {
          if (that.ws && that.ws.readyState === 1) {
            that.ws.send(JSON.stringify(message));
            clearInterval(timer);
          }
        }, 200);

        const timer01 = setTimeout(() => {
          clearTimeout(timer01);
          clearInterval(timer);
        }, 10000);
      }
    }
  }

  // 重连时间间隔控制
  _generateInterval(k: number) {
    if (this.reconnectIntervalInMilliSeconds > 0) {
      return this.reconnectIntervalInMilliSeconds;
    }
    return Math.min(30, Math.pow(2, k) - 1) * 1000;
  }
}

const websocket = (options: string | Options) => new WebSocketClass(options);

export { websocket };
